import {celebrate, Joi} from "celebrate";
import {Request, Router} from 'express'
import ms = require("ms");
import * as request from 'superagent'
import {InstanceType} from 'typegoose'
import {isNullOrUndefined} from "util";
import {alipayCashier} from "../alipay/payment";
import {GameActivity, GameActivityModel} from "../database/models/gameActivity";
import {GamePrizeModel} from "../database/models/gamePrize";
import {LipstickGameOrderModel} from "../database/models/lipstickGameOrder";
import {LipstickSeries, LipstickSeriesModel} from "../database/models/lipstickSeries";
import {PressGameOrderModel} from "../database/models/pressGameOrder";
import {RMB_2_COIN_RATE} from "../database/models/Product";
import {PressGroup, PressGroupModel} from "../database/models/ProductGroup";
import createClient from "../utils/redis";
import {wechatPayCashier} from "../wechat/wechatCashier";
import {InternalError} from "./error";
import {LIPSTICK_GAME_REVIVE_TIME_UP, LIPSTICK_GAME_TIME_ALL, LipStickGame} from "./games/lipStickGame";
import {PRESS_GAME_TIME, PressGame} from "./games/pressGame";
import {RandomLuckBoxGame} from "./games/randomLuckBoxGame";
import {RotateEggGame} from "./games/rotateEggGame";
import {findProductGroupById} from "./helpers/productgroup"
import {Response, withActivity, withLipstickSeries, withPressGroup, withProductGroup, withUser} from "./middlewareType";
import {jwtAuthMiddleware} from "./passort";
import {CLIENT_TYPE, lipstickResumeTimes, pcUrl} from "./utils";

const gameRouter = Router()

const redisClient = createClient()

const gameStoreKey = id => `act:${id}`

async function createGameStorage(playerId: string, productId: string) {

  const key = gameStoreKey(playerId)
  return redisClient.multi()
    .hset(key, 'bid', productId)
    .expire(key, LIPSTICK_GAME_TIME_ALL)
    .exec_atomicAsync()
}

async function updateGameStore(id: string, game: any) {
  const key = gameStoreKey(id)
  return redisClient.setexAsync(key, LIPSTICK_GAME_REVIVE_TIME_UP, JSON.stringify(game))
}

async function fetchGameStore(id: string) {
  const key = gameStoreKey(id)
  const data = await redisClient.getAsync(key)
  return JSON.parse(data)
}

gameRouter.post('/start/:lipstickSeriesId',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({
      lipstickSeriesId: Joi.string().required()
    }),
    body: Joi.object().keys({
      clientType: Joi.string()
    })
  }),
  async (req: Request & withUser, res, next) => {

    const latestActivity: InstanceType<GameActivity> = await GameActivityModel
      .findOne({player: req.user._id, from: `lipstick`}).sort({endAt: -1})

    if (!latestActivity ||
      latestActivity.state === 'done' ||
      Date.now() + 1000 >= latestActivity.endAt.getTime()) {
      next()
    } else {
      // 这步获取上次活动
      return res.json({
        ok: true,
        data: {
          new: false,
          canWin: latestActivity.canWin,
          lipstickSeries: latestActivity.lipstickSeries,
          activity: latestActivity._id,
          endAt: latestActivity.endAt
        }
      })
    }
  },
  async (req: Request & withLipstickSeries, res: Response, next) => {

    try {
      const l: InstanceType<LipstickSeries> = await LipstickSeriesModel.findOne({_id: req.params.lipstickSeriesId})
      if (l) {

        if (l.onStock) {
          req.lipstickSeries = l
          next()
        } else {
          res.error(InternalError("NO_ON_STOCK"))
        }
      } else {
        res.error(InternalError("NO_SUCH_SERIES"))
      }
    } catch (e) {
      res.error(InternalError("NO_SUCH_SERIES"))
    }
  },

  async (req: Request & withUser & withLipstickSeries, res, next) => {
    if (isNullOrUndefined(req.body.clientType) || req.body.clientType === CLIENT_TYPE.NORMAL) {
      if (req.user.coin + req.user.freeCoin < req.lipstickSeries.priceInCoin)
        return res.json(
          {
            ok: false, error: 'NO_ENOUGH_COIN',
            msg: '金币不足'
          })
      else
        next()
    } else if (req.body.clientType === CLIENT_TYPE.PUCHENG) {
      const re = await request.post(pcUrl + `/provideForOtherGame/gemIsEnough`)
        .send({
          gem: req.lipstickSeries.priceInFangka,
          wechatUnionid: req.user.wechatUnionid,
        })
        .then(r => {
          if (r && r.body && r.body.ok) {
            return r.body
          } else {
            return {ok: false}
          }
        }).catch(e => {
          console.error(pcUrl + '/provideForOtherGame/gemIsEnough ERROR', e)
          return {ok: false}
        })
      if (!re || !re.ok)
        return res.json(
          {
            ok: false, error: 'NO_ENOUGH_COIN',
            msg: '房卡不足'
          })
      else
        next()
    }
  },

  async (req: Request & withLipstickSeries & withUser, res: Response) => {

    try {
      const game = new LipStickGame(req.user, req.lipstickSeries, req.body.clientType || CLIENT_TYPE.NORMAL)

      const activity = await game.start()

      res.json({
        ok: true, data: {
          new: true,
          canWin: activity.canWin,
          activity: activity._id,
          endAt: activity.endAt,
          lipstickSeries: req.lipstickSeries._id
        }
      })
    } catch (e) {
      res.error(e)
    }
  }
)

gameRouter.post('/RMBStart/:lipstickSeriesId',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({lipstickSeriesId: Joi.string().required()}),
    body: Joi.object().keys({
      payType: Joi.string().required().valid(['wechatPay', 'aliPay'])
    })
  }),
  async (req: Request & withLipstickSeries, res: Response, next) => {
    return

    // @ts-ignore
    const l: InstanceType<LipstickSeries> = await LipstickSeriesModel
      .findOne({_id: req.params.lipstickSeriesId})
    if (!l)
      return res.error(InternalError("NO_SUCH_SERIES"))
    if (!l.onStock)
      return res.error(InternalError("NO_ON_STOCK"))

    req.lipstickSeries = l
    next()
  },
  async (req: Request & withLipstickSeries & withUser, res: Response) => {

    try {
      const lipstickSeries = req.lipstickSeries

      const lgo = await LipstickGameOrderModel.create({
        lipstickSeries,
        player: req.user.id,
        state: 'unpaid',
        payFor: 'start',
        createAt: new Date()
      })

      const paymentArg = {
        title: 'lipstickGame start',
        detail: lipstickSeries.name,
        price: lipstickSeries.priceInCoin / RMB_2_COIN_RATE,
        order: lgo.id,
        type: 'LipstickGameOrder',
        fromIP: req.ip
      }

      const cashier = req.body.payType === 'wechatPay' ? wechatPayCashier : alipayCashier

      const payOrder = await cashier.createPaymentOrder(paymentArg, ms('15min'))

      res.json({
        ok: true,
        data: {
          lipstickGameOrder: lgo,
          payOrder: payOrder.prepay
        }
      })

    } catch (e) {
      res.error(e)
    }
  }
)

gameRouter.get('/lipstickOrder/:lipstickGameOrder',
  celebrate({
    params: Joi.object({
      lipstickGameOrder: Joi.string().required()
    })
  }),
  async (req, res: Response) => {
    return

    // @ts-ignore
    try {
      const lipstickGameOrder =
        await LipstickGameOrderModel.findById(req.params.lipstickGameOrder)
          .populate(`activity`)
          .lean()

      if (!lipstickGameOrder.activity || lipstickGameOrder.state === `unpaid`)
        return res.json({ok: false})

      return res.json({ok: true, data: {activity: lipstickGameOrder.activity}})
    } catch (e) {
      res.error(e)
    }
  })

gameRouter.put('/activity/state/:activity', celebrate({
    body: Joi.object()
  })
  , async (req, res) => {

    await updateGameStore(req.params.activity, req.body)

    res.json({
      ok: true
    })
  })

gameRouter.get('/activity/state/:activity'
  , async (req, res: Response) => {

    const data = await fetchGameStore(req.params.activity)
    if (!data)
      return res.error(InternalError("GAME_TIME_UP"))

    res.json({
      ok: true, data
    })
  })

gameRouter.get('/my/activity/:gameType',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({
      gameType: Joi.string().required().valid(['lipstick', 'pressAccurate'])
    })
  }),
  async (req: Request & withUser, res) => {
    const act: InstanceType<GameActivity> = await GameActivityModel.findOne({
      player: req.user._id,
      from: req.params.gameType
    })
      .sort({createAt: -1})
      .populate('lipstickSeries')
      .populate(`pressGroup`)
      .lean()
    if (act) {
      if (act.endAt.getTime() <= Date.now() || act.state === 'done') {
        res.json({
          ok: true, data: {
            activity: null
          }
        })
      } else {
        res.json({
          ok: true, data: {
            activity: act
          }
        })
      }
    } else {
      res.json({
        ok: true, data: {
          activity: null
        }
      })
    }

  })

gameRouter.post('/claim/:activity',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      win: Joi.boolean(),
      stage: Joi.number().integer().valid([0, 1, 2]).required(),
      clientType: Joi.string()
    })
  }),
  async (req: Request & withUser & withLipstickSeries & withActivity, res: Response, next) => {

    const act: InstanceType<GameActivity> = await GameActivityModel
      .findById(req.params.activity)
      .populate('lipstickSeries')

    if (act) {
      if (act.state === 'done')
        return res.error(InternalError("ALREADY_CLAIMED"))

      if (!act.canWin && req.body.win)
        return res.error(InternalError("FAKE_CLIENT"))

      if (act.player.toString() !== req.user.id)
        return res.error(InternalError("FAKE_CLIENT"))

      if (req.body.stage < act.stage)
        return res.error(InternalError("FAKE_CLIENT"))

      req.activity = act
      req.lipstickSeries = act.lipstickSeries as InstanceType<LipstickSeries>
      next()
    } else {
      res.error(InternalError("FAKE_CLIENT"))
    }
  },

  async (req: Request & withUser & withLipstickSeries & withActivity, res: Response) => {

    const game = new LipStickGame(req.user, req.lipstickSeries, req.body.clientType || CLIENT_TYPE.NORMAL)
    const {ok, data} = await game.claim(req.activity, req.body.win, req.body.stage)
    if (!ok)
      return res.error(InternalError(data.message))

    res.json({
      ok: true,
      data: {
        win: data.win,
        lipstickSeries: req.lipstickSeries,
        gift: data.littleGift,
        resumeTimes: lipstickResumeTimes - data.activity.resumeTimes
      }
    })
  }
)

gameRouter.post('/luckBoxResult/:groupId',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      draws: Joi.number().integer().valid([1, 3, 6, 9, 15]).required()
    })
  }),
  async (req: Request & withProductGroup, res: Response, next) => {
    const pg = await findProductGroupById(req.params.groupId)
    if (pg) {
      req.productGroup = pg
      next()
    } else {
      res.error(InternalError('NO_SUCH_PRODUCT_GROUP'))
    }
  },
  async (req: Request & withUser & withProductGroup, res: Response) => {
    try {
      const game = new RandomLuckBoxGame(req.user, req.productGroup, req.body.draws)
      const gifts = await game.start()

      res.json({
        ok: true,
        data: {
          prizes: gifts,
        }
      })
    } catch (e) {
      res.error(e)
    }
  }
)

gameRouter.post('/moneyIsEnough/:groupId',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      draws: Joi.number().integer().required()
    })
  }),
  async (req: Request & withProductGroup, res: Response, next) => {
    const pg = await findProductGroupById(req.params.groupId)
    if (pg) {
      req.productGroup = pg
      next()
    } else {
      res.error(InternalError('NO_SUCH_PRODUCT_GROUP'))
    }
  },
  async (req: Request & withUser & withProductGroup, res: Response) => {
    try {

      if (req.productGroup.priceInGem <= 0)
        throw InternalError("FAKE_CLIENT")

      if (req.user.gem < req.productGroup.priceInGem * req.body.draws)
        throw InternalError("NO_ENOUGH_GEM")

      res.json({
        ok: true,
        data: {
          info: ``
        }
      })

    } catch
      (e) {
      res.error(e)
    }
  }
)

gameRouter.post('/eggdraw/:groupId',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      draws: Joi.number().integer().valid([1, 3, 10]).required()
    })
  }),
  async (req: Request & withProductGroup, res: Response, next) => {
    const pg = await findProductGroupById(req.params.groupId)
    if (pg) {
      req.productGroup = pg
      next()
    } else {
      res.error(InternalError('NO_SUCH_PRODUCT_GROUP'))
    }
  },
  async (req: Request & withUser & withProductGroup, res: Response) => {
    try {
      const game = new RotateEggGame(req.user, req.productGroup, req.body.draws)

      const gifts = await game.start()

      res.json({
        ok: true,
        data: {
          prizes: gifts,
        }
      })
    } catch (e) {
      res.error(e)
    }
  }
)

gameRouter.post('/pressAccurateStart/:pressGroupId',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({
      pressGroupId: Joi.string().required(),
    }),
    body: Joi.object().keys({
      clientType: Joi.string()
    })
  }),
  async (req: Request & withUser, res, next) => {

    const latestActivity: InstanceType<GameActivity> = await GameActivityModel
      .findOne({player: req.user._id, from: `pressAccurate`}).sort({endAt: -1})

    // 这步获取上次活动
    if (!latestActivity ||
      latestActivity.state === 'done' ||
      latestActivity.endAt.getTime() <= Date.now()) {
      next()
    } else {
      return res.json({
        ok: true,
        data: {
          new: false,
          winLevel: latestActivity.winLevel,
          pressGroup: latestActivity.pressGroup,
          activity: latestActivity._id,
          endAt: latestActivity.endAt
        }
      })
    }
  },
  async (req: Request & withPressGroup, res: Response, next) => {

    try {
      const pg: InstanceType<PressGroup> =
        await PressGroupModel.findOne({_id: req.params.pressGroupId})
      if (pg) {
        if (pg.state !== `off`) {
          req.pressGroup = pg
          next()
        } else
          res.error(InternalError("NO_SUCH_PRODUCT_GROUP"))
      } else {
        res.error(InternalError("NO_SUCH_PRODUCT_GROUP"))
      }
    } catch (e) {
      res.error(InternalError("NO_SUCH_PRODUCT_GROUP"))
    }
  },

  async (req: Request & withUser & withPressGroup, res, next) => {
    if (isNullOrUndefined(req.body.clientType) || req.body.clientType === CLIENT_TYPE.NORMAL) {
      if (req.user.coin + req.user.freeCoin < req.pressGroup.priceInCoin)
        return res.json(
          {
            ok: false, error: 'NO_ENOUGH_COIN',
            msg: '金币不足'
          })
      else
        next()
    } else if (req.body.clientType === CLIENT_TYPE.PUCHENG) {
      const re = await request.post(pcUrl + `/provideForOtherGame/gemIsEnough`)
        .send({
          gem: req.pressGroup.priceInFangka,
          wechatUnionid: req.user.wechatUnionid,
        })
        .then(r => {
          if (r && r.body && r.body.ok) {
            return r.body
          } else {
            return {ok: false}
          }
        }).catch(e => {
          console.error(pcUrl + '/provideForOtherGame/gemIsEnough ERROR', e)
          return {ok: false}
        })
      if (!re || !re.ok)
        return res.json(
          {
            ok: false, error: 'NO_ENOUGH_COIN',
            msg: '房卡不足'
          })
      else
        next()
    }
  },

  async (req: Request & withPressGroup & withUser, res: Response) => {

    try {
      const game = new PressGame(req.user, req.pressGroup, req.body.clientType || CLIENT_TYPE.NORMAL)

      const activity = await game.start()

      res.json({
        ok: true, data: {
          new: true,
          winLevel: activity.winLevel,
          activity: activity._id,
          endAt: activity.endAt,
          pressGroup: req.pressGroup._id
        }
      })
    } catch (e) {
      res.error(e)
    }
  }
)

gameRouter.post('/pressAccurateRMBStart/:pressGroupId',
  jwtAuthMiddleware,
  celebrate({
    params: Joi.object({pressGroupId: Joi.string().required()}),
    body: Joi.object().keys({
      payType: Joi.string().required().valid(['wechatPay', 'aliPay'])
    })
  }),
  async (req: Request & withPressGroup, res: Response, next) => {
    return

    // @ts-ignore
    const pg: InstanceType<PressGroup> = await PressGroupModel
      .findOne({_id: req.params.pressGroupId})
    if (!pg)
      return res.error(InternalError("NO_SUCH_PRODUCT_GROUP"))
    if (pg.state === `off`)
      return res.error(InternalError("NO_ON_STOCK"))

    req.pressGroup = pg
    next()
  },
  async (req: Request & withPressGroup & withUser, res: Response) => {

    try {
      const pressGroup = req.pressGroup

      const pgo = await PressGameOrderModel.create({
        pressGroup,
        player: req.user.id,
        state: 'unpaid',
        payFor: 'start',
        createAt: new Date()
      })

      const paymentArg = {
        title: 'pressGame start',
        detail: pressGroup.name,
        price: pressGroup.priceInCoin / RMB_2_COIN_RATE,
        order: pgo.id,
        type: 'PressGameOrder',
        fromIP: req.ip
      }

      const cashier = req.body.payType === 'wechatPay' ? wechatPayCashier : alipayCashier

      const payOrder = await cashier.createPaymentOrder(paymentArg, ms('15min'))

      res.json({
        ok: true,
        data: {
          pressGameOrder: pgo,
          payOrder: payOrder.prepay
        }
      })

    } catch (e) {
      res.error(e)
    }
  }
)

gameRouter.get('/pressGameOrder/:pressGameOrder',
  celebrate({
    params: Joi.object({
      pressGameOrder: Joi.string().required()
    })
  }),
  async (req, res: Response) => {

    return
    // @ts-ignore
    try {
      const pressGameOrder =
        await PressGameOrderModel.findById(req.params.pressGameOrder)
          .populate(`activity`)
          .lean()

      if (!pressGameOrder.activity || pressGameOrder.state === `unpaid`)
        return res.json({ok: false})

      return res.json({ok: true, data: {activity: pressGameOrder.activity}})
    } catch (e) {
      res.error(e)
    }
  })

gameRouter.post('/pressAccurateClaim/:activity',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      winLevel: Joi.number().required(),
      clientType: Joi.string()
    })
  }),

  async (req: Request & withUser & withPressGroup & withActivity, res: Response, next) => {

    const act: InstanceType<GameActivity> = await GameActivityModel
      .findById(req.params.activity)
      .populate('lipstickSeries')
      .populate('pressGroup')

    if (act) {
      if (act.state === 'done')
        return res.json({ok: false, error: 'ALREADY_CLAIMED', msg: '游戏已结束'})

      if (Date.now() > act.endAt.getTime() + PRESS_GAME_TIME * 1000)
        return res.json({ok: false, error: 'ALREADY_CLAIMED', msg: '游戏已结束'})

      if (req.body.winLevel !== 1 && req.body.winLevel > act.winLevel)
        return res.error(InternalError("FAKE_CLIENT"))

      if (act.player.toString() !== req.user.id)
        return res.error(InternalError("NO_SUCH_USER"))

      req.activity = act
      req.pressGroup = act.pressGroup as InstanceType<PressGroup>
      next()
    } else {
      res.error(InternalError("FAKE_CLIENT"))
    }
  },

  async (req: Request & withUser & withPressGroup & withActivity, res: Response) => {

    const game = new PressGame(req.user, req.pressGroup, req.body.clientType || CLIENT_TYPE.NORMAL)
    const {ok, data} = await game.claim(req.activity, req.body.winLevel)

    if (!ok)
      return res.error(InternalError(data.message))

    const {winLevel, gamePrize} = data

    let prize = null
    if (gamePrize)
      prize = await GamePrizeModel.findById(gamePrize.id).populate(`product`).lean()

    res.json({
      ok: true,
      data: {
        winLevel,
        prize
      }
    })
  }
)

export default gameRouter
